{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению\n",
    "<center>Автор материала: Екатерина Ширяева slackname: Katya.Shiryaeva"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#  Есть такая либа H20"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Согласно исследованиям [Gartner в феврале 2018](https://www.gartner.com/doc/reprints?id=1-4RQ3VEZ&ct=180223&st=sb), H2O занимает уверенное место в лидерах рынка среди DataScience и Machine Learning платформ.\n",
    "Gartner считают H2O.ai технологическим лидером, эта платформа мспользуется более чем 100000 data scientistами и удоволетверенность клиентами самая высокая (поддержка, обучение и продажи).  \n",
    "В этом обзоре я хочу показать отличия от реализаций алгоритмов в обычном sklearn"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"https://www.gartner.com/resources/326400/326456/326456_0001.png;wa8addaec1755a1f0c?reprintKey=1-4RQ3VEZ\" alt=\"Drawing\" style=\"width: 500px;\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Подготовка данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для примера можно взять датасет из [1ой лекции: отток клиентов телекома](https://habrahabr.ru/company/ods/blog/322626/)  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv('data/telecom_churn.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Описание всех признаков можно посмотреть в [1ой лекции](https://habrahabr.ru/company/ods/blog/322626/)  \n",
    "В качестве предварительной обработки заменю все значения churn на 0/1,   \n",
    "для International plan и Voice mail plan сделаю замену yes / no на 0 / 1  \n",
    "а категориальную переменную State пока удалю из обработки (данных недостаточно, чтобы сделать OHE, и укрупнение категорий не стоит в целях этого тьюториала)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d = {'No' : 0, 'Yes' : 1}\n",
    "df['Churn'] = df['Churn'].apply(lambda x : int(x))\n",
    "df['International plan'] = df['International plan'].map(d)\n",
    "df['Voice mail plan'] = df['Voice mail plan'].map(d)\n",
    "df.drop('State', axis=1, inplace=True)\n",
    "\n",
    "df.head() "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разделим на тест и обучающую выборки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(df.iloc[:,:-1], df.iloc[:,-1], test_size=0.3, random_state=42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для работы h2o необходимо установить эту библиотеку и запустить"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install h2o"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "import h2o\n",
    "\n",
    "h2o.init(nthreads=-1, max_mem_size=8)\n",
    "# nthreads - количество ядер процессора для вычислений\n",
    "# max_mem_size - максимальный размер оперативной памяти "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Все данные надо перевести в специальную структуру h2o : H2OFrame  \n",
    "h2o [поддерживает](http://docs.h2o.ai/h2o/latest-stable/h2o-docs/getting-data-into-h2o.html) большое количество источников, однако у меня не получилось перекодировать csr-матрицу из задания про Элис :)  очень долго висел :)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "training = h2o.H2OFrame(pd.concat([X_train, y_train], axis=1))\n",
    "validation = h2o.H2OFrame(pd.concat([X_test, y_test], axis=1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на структуру (это обязательно надо делать, т.к. не всегда корректно происходит переход форматов) <br>\n",
    "Здесь будут описаны основные параметры каждой переменной (тип, максимум, минимум, среднее, стандартное отклонение, количество нулевых и пропущенных значений и первые 10 наблюдений)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "training.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Зависимая переменная для бинарной классификации должна быть не количественной переменной, а категориальной, преобразуем с помощью метода asfactor()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "training['Churn'] = training['Churn'].asfactor()\n",
    "validation['Churn'] = validation['Churn'].asfactor()\n",
    "\n",
    "training['International plan'] = training['International plan'].asfactor()\n",
    "training['Voice mail plan'] = training['Voice mail plan'].asfactor()\n",
    "\n",
    "validation['International plan'] = validation['International plan'].asfactor()\n",
    "validation['Voice mail plan'] = validation['Voice mail plan'].asfactor()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Random Forest"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### sklearn RandomForestClassifier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.metrics import roc_auc_score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "forest = RandomForestClassifier(n_estimators=800, random_state=152, n_jobs=-1)\n",
    "forest.fit(X_train, y_train)\n",
    "print('AUC для sklearn RandomForestClassifier: {:.4f}'.format(roc_auc_score(y_test, forest.predict_proba(X_test)[:, 1])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### H2ORandomForestEstimator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from h2o.estimators import H2ORandomForestEstimator"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "необходимо задать список зависимых переменных и предикторов (это будут X и y в коде)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = training.columns\n",
    "X.remove('Churn')\n",
    "y = 'Churn'\n",
    "\n",
    "rf1 = H2ORandomForestEstimator(model_id='tutorial1', ntrees=800, seed=152)\n",
    "rf1.train(X, y, training_frame=training, validation_frame=validation)\n",
    "rf1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Получился большой вывод информации о модели.  \n",
    "Сразу следует обратить внимание на AUC при тех же заданных параметрах (ntrees=800)  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(rf1.auc(valid=True))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вся информация идет сначала об обучающей, потом о валидационной выборке\n",
    "* Отображаются все метрики (MSE, RMSE, LogLoss, AUC, Gini и т.д.)\n",
    "* Строится матрица ошибок (confusion matrix), которая приводится для порогового значения спрогнозированной вероятности события, оптимального с точки зрения F1-меры _(для справки : F1- мера = 2 x точность x полнота/(точность + полнота))_\n",
    "* _Maximum Metrics:_ Рассчитываются на ней различные метрики и соответствующие пороговые значения\n",
    "* _Gains/Lift Table:_ Таблица выигрышей создается путем разбиения данных на группы по квантильным пороговым значениям спрогнозированной верроятности положительного класса\n",
    "* _Variable Importances:_ Информация о важностях предикторов. Информация выводится также в отмасштабированном и процентном видах. На сайте с документацией указано, что важность рассчитывается, как относительное влияние каждой переменной: была ли переменная выбрана при построении дерева и на как изменилась среднеквадратичная ошибка (рассчитывается на всех деревьях)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# можно нарисовать графиком \n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.rcdefaults()\n",
    "fig, ax = plt.subplots()\n",
    "variables = rf1._model_json['output']['variable_importances']['variable']\n",
    "y_pos = np.arange(len(variables))\n",
    "scaled_importance = rf1._model_json['output']['variable_importances']['scaled_importance']\n",
    "ax.barh(y_pos, scaled_importance, align='center', color='green', ecolor='black')\n",
    "ax.set_yticks(y_pos)\n",
    "ax.set_yticklabels(variables)\n",
    "ax.invert_yaxis()\n",
    "ax.set_xlabel('Scaled Importance')\n",
    "ax.set_title('Variable Importance')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "___Описание параметров вызова функции___  \n",
    "1. Параметры, определяющие задачу  \n",
    "    * __model_id:__ идентификатор модели\n",
    "    * __training_frame:__ датасет для построения модели\n",
    "    * __validation_frame:__ датасет для валидации\n",
    "    * __nfolds:__ количество фолдов для кросс-валидации (по умолчанию 0)\n",
    "    * __y:__ имена зависимой переменной\n",
    "    * __x:__ список названий предикторов\n",
    "    * __seed:__ random state <br><br>\n",
    "2. Параметры, задающие сложность дерева\n",
    "    * __ntrees__: количество деревьев\n",
    "    * __max_depth:__  максимальная глубина дерева\n",
    "    * __min_rows:__ минимальное количество наблюдений в терминальном листе\n",
    " <br><br>\n",
    "3. Параметры, определяющие формирование подвыборок\n",
    "    * __mtries:__ количество случайно отбираемых предикторов для разбиения узла. По умолчанию -1: для классификации корень квадратный из р, для регрессии р / 3, где р - количество предикторов\n",
    "    * __sample_rate:__ какую часть строк отбирать (от 0 до 1). По умолчанию 0.6320000291 \n",
    "    * __sample_rate_per_class:__ для построения модели из несбалансированного набора данных. Какую часть строк выбирать для каждого дерева (от 0 до 1) \n",
    "    * __col_sample_rate_per_tree:__ какую часть столбцов выбирать для каждого дерева (от 0 до 1, по умоланчанию 1)\n",
    "    * __col_sample_rate_change_per_level:__ задает изменение отбора столбцов для каждого уровня дерева (от 0 до 2, по умолчанию 1), например: (factor = col_sample_rate_change_per_level)\n",
    "        * level 1: col_sample_rate\n",
    "        * level 2: col_sample_rate * factor\n",
    "        * level 3: col_sample_rate * factor^2\n",
    "        * level 4: col_sample_rate * factor^3\n",
    " <br><br>\n",
    "4. Параметры, определяющие биннинг переменных\n",
    "    * __nbins__: (Numerical/real/int only) для каждой переменной строит по крайней мере n интервалов и затем рабивает по наилучшей точке расщепления\n",
    "    * __nbins_top_level:__  (Numerical/real/int only) определяет максимальное количество интервалов n на вершине дерева. При переходе на увроень ниже делит заданное число на 2 до тех пор, пока не дойдет до уровня nbins \n",
    "    * __nbins_cats:__ (Categorical/enums only) каждая переменная может быть разбита максимум на n интерваловю. Более высокие значения могут привести к переобучению.\n",
    "    * __categorical_encoding:__ схема кодировки для категориальных переменных\n",
    "        * auto: используется схема enum.\n",
    "        * enum: 1 столбец для каждого категориального признака (как есть)\n",
    "        * one_hot_explicit: N+1 новых столбцов для каждого признака с N уровнями\n",
    "        * binary: не более 32 столбцов для признака (используется хеширование)\n",
    "        * eigen: выполняет one-hot-encoding и оставляет k главных компонент\n",
    "        * label_encoder: все категории сортируются в лексикографическом порядке и каждому уровню присваивается целое число, начиная с 0 (например, level 0 -> 0, level 1 -> 1,  и т.д.)\n",
    "        * sort_by_response: все категории сортируются по среднему значению переменной и каждому уровню присваивается значение (например, для уровня с мин средним ответом -> 0, вторым наименьшим -> 1, и т.д.). \n",
    "    * __histogram_type:__ тип гистограммы для поиска оптимальных расщепляющих значений\n",
    "        * AUTO = UniformAdaptive\n",
    "        * UniformAdaptive: задаются интервалы одинаковой ширины (max-min)/N\n",
    "        * QuantilesGlobal: интервалы одинаквого размера (в каждом интервале одинаковое количество наблюдений)\n",
    "        * Random: задает построение Extremely Randomized Trees (XRT). Случайным образом отбирается N-1 точка расщепления и затем выбирается наилучшее разбиение\n",
    "        * RoundRobin: все типы гистограмм (по одному на каждое дерево) перебираются по кругу \n",
    " <br><br>\n",
    "5. Параметры остановки\n",
    "    * __stopping_rounds:__ задает количество шагов в течение которого должно произойти заданное улучшение(stopping_tolerance:) для заданной метрики (stopping_metric) \n",
    "    * __stopping_metric:__  метрика для ранней остановки (по умолчанию логлосс для классификации и дисперсия - для регрессии). Возможные значения: deviance, logloss, mse, rmse, mae, rmsle, auc, lift_top_group, misclassification, mean_per_class_error\n",
    "    * __stopping_tolerance:__ относительное улучшение для остановки обучения (если меньше, то остановка)\n",
    "\n",
    "  \n",
    "  \n",
    "  \n",
    "Полный список всех параметров с описанием можно найти на сайте с [официальной документацией](http://docs.h2o.ai/h2o/latest-stable/h2o-docs/data-science/drf.html)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=green>Отдельно стоит отметить, что биннинг переменных в h2o позволяет добиться улучшения качества и скорости построения леса. </font>\n",
    "\n",
    "__Количественные переменные__\n",
    "В классической реализации случайного леса для количественного предиктора с k категориями может быть рассмотрено k-1 вариантов разбиения. (Все значения сортируются и средние значения по каждой паре рассматриваются в качестве точек расщепления).  \n",
    "В h2o для каждой переменной определяются типы и количество интервалов для разбиения, т.е. создается гистограмма (тип : histogram_type), и количество регулируется двумя параметрами:  nbins и nbins_top_level. Перебор этих параметров позволит улучшить качество леса.\n",
    "\n",
    "__Качественные переменные__\n",
    "В классической реализации случайного леса для категориального предиктора с k категориями может быть рассмотрено ${2^{k-1}} - 1$ разбиения (всеми возможными способами).  \n",
    "В h2o для каждой качественной переменной также определюется типы и количество интервалов для разбиения. Количество регулируется : nbins_cats и nbins_top_level (при переходе на уровень ниже значение nbins_top_level уменьшается в 2 раза до тех пор пока значение больше nbins_cats).  \n",
    "Пример как происходит разбиение на бины:  \n",
    "Если количество  категорий меньше значения параметра nbins_cats, каждая категория\n",
    "получает свой бин. Допустим, у нас есть переменная Class. Если у нее\n",
    "есть уровни A, B, C, D, E, F, G и мы зададим nbins_cats=8, то будут\n",
    "сформировано 7 бинов: {A}, {B}, {C}, {D}, {E}, {F} и {G}. Каждая категория\n",
    "получает свой бин. Будет рассмотрено ${2^6}-1=63$ точки расщепления. Если\n",
    "мы зададим nbins_cats=10, то все равно будут получены те же самые\n",
    "бины, потому что у нас всего 7 категорий. Если количество категорий\n",
    "больше значения параметра nbins_cats, категории будут сгруппированы\n",
    "в бины в лексикографическом порядке. Например, если мы зададим\n",
    "nbins_cats=2, то будет сформировано 2 бина: {A, B, C, D} и {E, F, G}. У\n",
    "нас будет одна точка расщепления. A, B, C и D попадут в один и тот же\n",
    "узел и будут разбиты только на последующем, более нижнем уровне или\n",
    "вообще не будут разбиты.  \n",
    "  \n",
    "Этот параметр очень важен для настройки, при больших значениях параметра мы можем получить дополнительную случайность в построении "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И в завершение этой части сделаем подбор параметров и попытаемся улучшить модель по разным параметрам"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from h2o.grid.grid_search import H2OGridSearch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rf_params = {'max_depth': [10, 14, 18, 24]}\n",
    "\n",
    "rf_grid = H2OGridSearch(model=H2ORandomForestEstimator(ntrees=800, seed=152),\n",
    "                          grid_id='rf_grid_max_depth',\n",
    "                          hyper_params=rf_params)\n",
    "rf_grid.train(X, y, training_frame=training, validation_frame=validation)\n",
    "\n",
    "# модели, отсортированные по AUC\n",
    "rf_gridperf = rf_grid.get_grid(sort_by='auc', decreasing=True)\n",
    "print(rf_gridperf)\n",
    "\n",
    "# выберем лучшую модель и выведем AUC на тесте \n",
    "best_rf = rf_gridperf.models[0]\n",
    "print(best_rf.auc(valid=True))\n",
    "print(rf_gridperf.get_hyperparams(0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rf_params1 = {'mtries': [3, 5, 7, 9]}\n",
    "\n",
    "rf_grid1 = H2OGridSearch(model=H2ORandomForestEstimator(ntrees=800, seed=152, max_depth=14),\n",
    "                          grid_id='rf_grid_mtries1',\n",
    "                          hyper_params=rf_params1)\n",
    "rf_grid1.train(X, y, training_frame=training, validation_frame=validation)\n",
    "\n",
    "# модели, отсортированные по AUC\n",
    "rf_gridperf1 = rf_grid1.get_grid(sort_by='auc', decreasing=True)\n",
    "print(rf_gridperf1)\n",
    "\n",
    "# выберем лучшую модель и выведем AUC на тесте \n",
    "best_rf1 = rf_gridperf1.models[0]\n",
    "print(best_rf1.auc(valid=True))\n",
    "print(rf_gridperf1.get_hyperparams(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "теперь к настройке более специфических параметров"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rf_params2 = {'histogram_type': ['UniformAdaptive', 'Random', 'QuantilesGlobal', 'RoundRobin']}\n",
    "\n",
    "rf_grid2 = H2OGridSearch(model=H2ORandomForestEstimator(ntrees=800, seed=152, max_depth=14, mtries=7),\n",
    "                          grid_id='rf_grid_hist_type',\n",
    "                          hyper_params=rf_params2)\n",
    "rf_grid2.train(X, y, training_frame=training, validation_frame=validation)\n",
    "\n",
    "# модели, отсортированные по AUC\n",
    "rf_gridperf2 = rf_grid2.get_grid(sort_by='auc', decreasing=True)\n",
    "print(rf_gridperf2)\n",
    "\n",
    "# выберем лучшую модель и выведем AUC на тесте \n",
    "best_rf2 = rf_gridperf2.models[0]\n",
    "print('AUC for the best model: ', best_rf2.auc(valid=True))\n",
    "print(rf_gridperf2.get_hyperparams(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По типу гистограммы RoundRobin оказался самым оптимальным"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rf_params3 = {'col_sample_rate_per_tree': [0.5, 0.6, 0.7, 0.8, 0.9], \n",
    "             'sample_rate': [0.5, 0.6, 0.7, 0.8, 0.9] }\n",
    "\n",
    "rf_grid3 = H2OGridSearch(model=H2ORandomForestEstimator(ntrees=800, seed=152, max_depth=14, mtries=7,\n",
    "                                                       histogram_type='RoundRobin'),\n",
    "                          grid_id='rf_grid3',\n",
    "                          hyper_params=rf_params3)\n",
    "rf_grid3.train(X, y, training_frame=training, validation_frame=validation)\n",
    "\n",
    "# модели, отсортированные по AUC\n",
    "rf_gridperf3 = rf_grid3.get_grid(sort_by='auc', decreasing=True)\n",
    "print(rf_gridperf3)\n",
    "\n",
    "# выберем лучшую модель и выведем AUC на тесте \n",
    "best_rf3 = rf_gridperf3.models[0]\n",
    "print('AUC for the best model: ', best_rf3.auc(valid=True))\n",
    "print(rf_gridperf3.get_hyperparams(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Logistic Regression"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### sklearn LogisticRegression"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.linear_model import LogisticRegression"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "logreg = LogisticRegression().fit(X_train, y_train)\n",
    "print('AUC для sklearn LogisticRegression: {:.4f}'.format(roc_auc_score(y_test, logreg.predict_proba(X_test)[:, 1])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "проверим на отмасштабированных данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "scaler = StandardScaler()\n",
    "X_train_scaled = scaler.fit_transform(X_train)\n",
    "X_test_scaled = scaler.transform(X_test)\n",
    "\n",
    "logreg = LogisticRegression().fit(X_train_scaled, y_train)\n",
    "print('AUC для sklearn LogisticRegression Scaled: {:.4f}'.format(\n",
    "    roc_auc_score(y_test, logreg.predict_proba(X_test_scaled)[:, 1])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "результаты хуже, чем в лесу  \n",
    "займемся подбором параметров в H2O"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### H2OGeneralizedLinearEstimator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from h2o.estimators.glm import H2OGeneralizedLinearEstimator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# создаем экземпляр класса H2OGeneralizedLinearEstimator\n",
    "glm_model = H2OGeneralizedLinearEstimator(family= \"binomial\", seed=1000000)\n",
    "# обучаем модель\n",
    "glm_model.train(X, y, training_frame= training, validation_frame=validation)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "glm_model.model_performance().show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "str(glm_model.model_performance().auc())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "___Описание параметров вызова функции___  \n",
    "1. Параметры, определяющие задачу  \n",
    "    * __model_id:__ идентификатор \n",
    "    * __training_frame:__ датасет для построения модели\n",
    "    * __validation_frame:__ датасет для валидации\n",
    "    * __nfolds:__ количество фолдов для кросс-валидации (по умолчанию 0)\n",
    "    * __y:__ имена зависимой переменной\n",
    "    * __x:__ список названий предикторов\n",
    "    * __seed:__ random state \n",
    "    * __family:__ тип модели (gaussian, binomial, multinomial, ordinal, quasibinomial, poisson, gamma, tweedie   \n",
    "    * __solver:__ \n",
    "        * IRLSM: Iteratively Reweighted Least Squares Method - используется с небольшим количеством предикторов и для l1-регулярзации\n",
    "        * L_BFGS: Limited-memory Broyden-Fletcher-Goldfarb-Shanno algorithm - используется для данных в большим числом колонок\n",
    "        * COORDINATE_DESCENT, COORDINATE_DESCENT_NAIVE - экспериментальные\n",
    "        * AUTO: Sets the solver based on given data and parameters (default)\n",
    "        * GRADIENT_DESCENT_LH, GRADIENT_DESCENT_SQERR: используется только для family=Ordinal<br><br>\n",
    "2. Параметры, определяющие регуляризацию   \n",
    "для справки формула ElasticNet, объединяющая $L_1$ и $L_2$ регуляризацию\n",
    "$$\\large \\begin{array}{rcl}\n",
    "L &=& -\\mathcal{L} + \\lambda R\\left(\\textbf W\\right) \\\\\n",
    "&=& -\\mathcal{L} + \\lambda \\left(\\alpha \\sum_{k=1}^K\\sum_{i=1}^M w_{ki}^2 + \\left(1 - \\alpha\\right) \\sum_{k=1}^K\\sum_{i=1}^M \\left|w_{ki}\\right| \\right)\n",
    "\\end{array}$$ где $\\alpha \\in \\left[0, 1\\right]$\n",
    "\n",
    "    * __alpha:__  распределение между $L_1$ и $L_2$ регуляризацией. (1 - $L_1$, 0 - $L_2$)\n",
    "    * __lambda:__ сила регуляризации\n",
    "    * __lambda_search:__ True / False. Определяет стоит ли начинать поиск $\\lambda$, начиная с максимального значения\n",
    "    * __lambda_min_ratio:__ минимальное значение $\\lambda$, используемое при поиске $\\lambda$\n",
    "    * __nlambdas:__ количество шагов при поиске $\\lambda$ (по умолчанию 100)\n",
    " <br><br>\n",
    "3. Параметры, влияющие на предобработку предикторов\n",
    "    * __standardize:__ использовать ли масштабирование\n",
    "    * __missing_values_handling:__ как работать с пропцщенными значениями (пропускать или испутировать средним)\n",
    "    * __remove_collinear_columns:__ удалять ли автоматически коллинеарные столбцы при построении модели \n",
    "    * __interactions:__ список колонок, из которых буду составлены все возможные пары и использованы для построения модели \n",
    "    * __interaction_pairs:__ список уже готовых пар для модели \n",
    " <br><br>\n",
    "\n",
    "Полный список всех параметров с описанием можно найти на сайте с [официальной документацией](http://docs.h2o.ai/h2o/latest-stable/h2o-docs/data-science/glm.html)\n",
    "  \n",
    "  \n",
    "Воспольуземся перебором параметров [H2OGridSearch] для поиска лучшей модели"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hyper_parameters = {'alpha': np.arange(0, 1.05, 0.05).tolist()}\n",
    "\n",
    "gridsearch = H2OGridSearch(H2OGeneralizedLinearEstimator(family='binomial', lambda_search=True, standardize=True),\n",
    "                           grid_id=\"gridresults\", hyper_params=hyper_parameters)\n",
    "gridsearch.train(X, y, training_frame= training, validation_frame=validation)\n",
    "\n",
    "gridperf = gridsearch.get_grid(sort_by=\"auc\", decreasing=True)\n",
    "best_model = gridperf.models[0]\n",
    "print(gridperf)\n",
    "\n",
    "print('AUC for the best model: ', best_model.auc(valid=True))\n",
    "print(best_model.summary()['regularization'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "H2O позволяет настроить силу регуляризации.  \n",
    "Для получения модели с более выоским AUC - надо попробовать предварительно разные способы масштабирования  \n",
    "И попробовать настроить еще один интересный параметр  : interactions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Gradient Boosting"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### sklearn GradientBoostingClassifier\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.ensemble import GradientBoostingClassifier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grb = GradientBoostingClassifier().fit(X_train, y_train)\n",
    "print('AUC для sklearn GradientBoostingClassifier: {:.4f}'.format(\n",
    "    roc_auc_score(y_test, grb.predict_proba(X_test)[:, 1])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Gradient Boosting Machine"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from h2o.estimators.gbm import H2OGradientBoostingEstimator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gbm = H2OGradientBoostingEstimator(ntrees = 800, learn_rate = 0.1,seed = 1234)\n",
    "gbm.train(X, y, training_frame= training, validation_frame=validation)\n",
    "gbm.model_performance().show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('AUC на валидационной выборке: ',gbm.auc(valid=True))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "___Описание параметров вызова функции___  \n",
    "только специфические, отличаюшиеся от RandomForest\n",
    "1. Параметры для GBM\n",
    "    * __learn_rate:__ скорость обучения (от 0 до 1)\n",
    "    * __learn_rate_annealing:__ уменьшает скорость обучения после построения каждого дерева\n",
    "    \n",
    "Для настройки параметров бустинга есть следующие советы от экспертов (Марк Лэндри, Дмитрий Ларько и [github](https://github.com/h2oai/h2o-3/blob/master/h2o-docs/src/product/tutorials/gbm/gbmTuning.ipynb)): \n",
    "* заификисровать количество деревьев константой, подобрать leraning rate (например, learn_rate=0.02, learn_rate_annealing=0.995) \n",
    "* в самом конце можно снова вернуться к настройке количества деревьев\n",
    "* потом необходимо настроить глубину деревьев (max_depth) (чаще всего это 4-10)\n",
    "* попробовать изменить тип гистрограммы, nbins/nbins_cat\n",
    "* изменить настройки, определяющие подвыборки (sample_rate, col_sample_rate), чаще всего это 70-80%\n",
    "* для несбалансированных наборов необходимо настроить параметры, отвечающие за баланс классов (sample_rate_per_class)\n",
    "* определить критерии остановки "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "начинаем настройку"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gbm_params = {'learn_rate': [0.0001, 0.001, 0.01, 0.1, 1]}\n",
    "\n",
    "gbm_grid = H2OGridSearch(model=H2OGradientBoostingEstimator(ntrees = 800, seed = 1234),\n",
    "                          grid_id='gbm_grid_learn_rate',\n",
    "                          hyper_params=gbm_params)\n",
    "gbm_grid.train(X, y, training_frame=training, validation_frame=validation)\n",
    "\n",
    "# модели, отсортированные по AUC\n",
    "gbm_gridperf = gbm_grid.get_grid(sort_by='auc', decreasing=True)\n",
    "print(gbm_gridperf)\n",
    "\n",
    "# выберем лучшую модель и выведем AUC на тесте \n",
    "best_gbm = gbm_gridperf.models[0]\n",
    "print(best_gbm.auc(valid=True))\n",
    "print(gbm_gridperf.get_hyperparams(0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gbm_params1 = {'max_depth': [4, 6, 8, 10]}\n",
    "\n",
    "gbm_grid1 = H2OGridSearch(model=H2OGradientBoostingEstimator(ntrees = 800, seed = 1234, learn_rate=0.001),\n",
    "                          grid_id='gbm_grid_max_depth',\n",
    "                          hyper_params=gbm_params1)\n",
    "gbm_grid1.train(X, y, training_frame=training, validation_frame=validation)\n",
    "\n",
    "# модели, отсортированные по AUC\n",
    "gbm_gridperf1 = gbm_grid1.get_grid(sort_by='auc', decreasing=True)\n",
    "print(gbm_gridperf1)\n",
    "\n",
    "# выберем лучшую модель и выведем AUC на тесте \n",
    "best_gbm1 = gbm_gridperf1.models[0]\n",
    "print(best_gbm1.auc(valid=True))\n",
    "print(gbm_gridperf1.get_hyperparams(0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gbm_params2 = {'nbins': [2, 8, 16, 32],\n",
    "               'histogram_type': ['UniformAdaptive', 'Random', 'QuantilesGlobal', 'RoundRobin']}\n",
    "\n",
    "gbm_grid2 = H2OGridSearch(model=H2OGradientBoostingEstimator(ntrees = 800, seed = 1234, \n",
    "                                                             max_depth=6, learn_rate=0.001),\n",
    "                          grid_id='gbm_grid_hist',\n",
    "                          hyper_params=gbm_params2)\n",
    "gbm_grid2.train(X, y, training_frame=training, validation_frame=validation)\n",
    "\n",
    "# модели, отсортированные по AUC\n",
    "gbm_gridperf2 = gbm_grid2.get_grid(sort_by='auc', decreasing=True)\n",
    "print(gbm_gridperf2)\n",
    "\n",
    "# выберем лучшую модель и выведем AUC на тесте \n",
    "best_gbm2 = gbm_gridperf2.models[0]\n",
    "print(best_gbm2.auc(valid=True))\n",
    "print(gbm_gridperf2.get_hyperparams(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В итоге AUC = 0.9450 на валидационной выборке получился немного выше для GradientBoostingMachine, чем для RandomForestClassifier AUC = 0.941686"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### XGBoost"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Еще одним из алгоритмов, доступных в H20, из семейства Gradient Boosting Machine является XGBoost. Эта библиотека - очень популярный инструмент для решения Kaggle-задач. Он позволяет строить деервья параллельно, что позволяет решать проблемы скорости обучения. \n",
    "\n",
    "__Особенности модели__\n",
    "\n",
    "Реализация модели поддерживает особенности реализации scikit-learn и R с новыми дополнениями, такими как регуляризация. Поддерживаются три основные формы повышения градиента:\n",
    "\n",
    "* Алгоритм Gradient Boosting также называется градиентной машиной повышения, включая скорость обучения.\n",
    "* Stochastic Gradient Boosting с суб-выборкой в строке, столбце и столбце на каждый уровень разделения.\n",
    "* Регулярное усиление градиента с регуляцией L1 и L2.\n",
    "\n",
    "__Системные функции__\n",
    "\n",
    "Библиотека предоставляет систему для использования в различных вычислительных средах, не в последнюю очередь:\n",
    "\n",
    "* Параллелизация построения дерева с использованием всех ваших ядер процессора во время обучения.\n",
    "* Распределенные вычисления для обучения очень крупных моделей с использованием кластера машин.\n",
    "* Внекорпоративные вычисления для очень больших наборов данных, которые не вписываются в память.\n",
    "* Кэш Оптимизация структуры данных и алгоритма для наилучшего использования аппаратного обеспечения.\n",
    "\n",
    "__Особенности алгоритма__\n",
    "\n",
    "Реализация алгоритма была разработана для эффективности вычислительных ресурсов времени и памяти. Цель проекта заключалась в том, чтобы наилучшим образом использовать имеющиеся ресурсы для обучения модели. Некоторые ключевые функции реализации алгоритма включают:\n",
    "\n",
    "* Редкая реализация Aware с автоматической обработкой отсутствующих значений данных.\n",
    "* Блочная структура для поддержки распараллеливания конструкции дерева.\n",
    "* Продолжение обучения, чтобы вы могли еще больше повысить уже установленную модель для новых данных. \n",
    "\n",
    "В реальности в H2O используется нативный алгоритм [XGBoost](http://xgboost.readthedocs.io/en/latest/get_started/index.html), поэтому не буду пытатьтся подобрать параметры, покажу только, как запускается"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from h2o.estimators import H2OXGBoostEstimator\n",
    "\n",
    "# библиотека доступна не для всех платформ, сначала надо проверить ее доступность\n",
    "is_xgboost_available = H2OXGBoostEstimator.available()\n",
    "print(is_xgboost_available)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "param = {\n",
    "      \"ntrees\" : 100\n",
    "    , \"max_depth\" : 10\n",
    "    , \"learn_rate\" : 0.02\n",
    "    , \"sample_rate\" : 0.7\n",
    "    , \"col_sample_rate_per_tree\" : 0.9\n",
    "    , \"min_rows\" : 5\n",
    "    , \"seed\": 4241\n",
    "    , \"score_tree_interval\": 100\n",
    "}\n",
    "\n",
    "model = H2OXGBoostEstimator(**param)\n",
    "model.train(X, y, training_frame=training, validation_frame=validation)\n",
    "model.model_performance().show()\n",
    "\n",
    "print('AUC на валидационной выборке GradientBoostingMachine: ',gbm.auc(valid=True))\n",
    "print('AUC на валидационной выборке XGBoost: ',model.auc(valid=True))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### LightGBM"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[LightGBM](https://github.com/Microsoft/LightGBM) строит глубокие асимметричные деревья, повторно разбивая один лист вместо разбиения всех листьев одного уровня до достижения максимальной глубины. \n",
    "XGBoost использует предварительную сортировку и гистограммирование для расчета наилучшего разбиения, т.е.\n",
    "- для каждого узла необходимо пронумеровать все признаки; \n",
    "- для каждого признака необходимо провести сортировку всех значений (здесь можно разбить на бины и провести сортировку бинов); \n",
    "- ищем налиучшее разбиение для признака; \n",
    "- выбираем наилучшее разбиение среди всех признаков.\n",
    "\n",
    "но LightGBM использует другой способ: градиент - это угол наклона функции потерь, таким образом, если градиент для каких-то точек больше, то эти точки важнее для поиска оптимального разбиения. Алгоритм находит все такие точки с максимальным градиентом и делает рандомное расщепление на точках с маленький градиентом.  \n",
    "Предположим, есть 500K строчек данных, где у 10k строчек градиент больше, таким образом алогритм выберет 10k строчку большим градиентом + x% от отсавшихся 490k строчек, выбранных случайно. Предположим, x = 10%, общее количество выбранных строк = 59k из 500K\n",
    "Важное предположение здесь: ошибка на тренировочном наборе с меньшим градиентом меньше и эти данные уже хорошо обучены в модели. Таким образом мы уменьшаем с одной стороны количество данных для обучения, но при этом сохраняем качество для уже обученных деревьев.\n",
    "\n",
    "----------------\n",
    "H2O не интегрирован с LightGBM, но предоставляет метод для эмуляции LightGBM алгоритма, используя определенный набор параметров:\n",
    "\n",
    "\n",
    "tree_method=\"hist\"\n",
    "grow_policy=\"lossguide\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "h2o.cluster().shutdown()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Список литературы__\n",
    "\n",
    "* https://www.h2o.ai/  \n",
    "* http://statistica.ru/local-portals/actuaries/obobshchennye-lineynye-modeli-glm/  \n",
    "* https://en.wikipedia.org/wiki/Generalized_linear_model  \n",
    "* https://alexanderdyakonov.files.wordpress.com/2017/06/book_boosting_pdf.pdf  \n",
    "* https://github.com/h2oai/h2o-3/blob/master/h2o-docs/src/product/tutorials/gbm/gbmTuning.ipynb  \n",
    "* https://ru.bmstu.wiki/XGBoost  \n",
    "* Артем Груздев. Прогнозное моделирование в IBM SPSS Statistics, R и Python; Лекции\n",
    "* https://towardsdatascience.com/catboost-vs-light-gbm-vs-xgboost-5f93620723db"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
